<?php

	namespace org\tekuna\plugin\pdodb;

	use \PDO;
	
	use org\tekuna\base\Tekuna;
	
	use org\tekuna\core\configuration\Configuration;
	use org\tekuna\core\configuration\ConfigurationElement;
	use org\tekuna\core\configuration\ConfigurationException;
	use org\tekuna\core\context\Context;
	use org\tekuna\core\context\ContextObjectSupplied;
	use org\tekuna\core\context\ContextInitialized;
	
	
	/**
	 * The ConnectionManager provides objects for configured conections. It
	 * is available in the application context as PdoConnectionManager. In XML
	 * a connection definition would look like this:
	 * 
	 * <pdo-db:connection
	 *    id="myConnection"
	 *    default="true"
	 *    dsn="mysql:dbname=testdb;host=127.0.0.1"
	 *    user="dbUser"
	 *    password="secret" />
	 *    
	 * The id gives the connection an identifier. With this identifier you can
	 * get connections from the ConnectionManager by calling getConnection($id).
	 * 
	 * The 'default' parameter is optional. If set to true, the particular connection
	 * is marked as the default connection. That connection is available by calling
	 * getDefaultConnection(). Special case: if there is only one connection defined,
	 * that connection is considered as default connection by default.
	 * 
	 * The parameters dsn, user and password are used as constructor parameters for
	 * the PDO object. See the documentation for more details:
	 * 
	 * http://php.net/manual/en/pdo.construct.php
	 * 
	 * Connections are initialized when requested at the first time (lazy).
	 */
	class ConnectionManager implements ContextObjectSupplied, ContextInitialized {
		
		private
			$objConfiguration,
			$arrConnectionDefinitions = array(),
			$arrConnections = array(),
			$sDefaultConnectionId = NULL;
			
		
		public function setConfiguration(Configuration $objConfiguration) {

			$this -> objConfiguration = $objConfiguration;
		}
		
		
		public function initialize() {
			
			// load all connection definitions
			$this -> arrConnectionDefinitions = $this -> objConfiguration -> getRootElement() -> getAllChildElements('pdo-db', 'connection');
		}
		
		
		/**
		 * Returns the connection identified with the given ID.
		 * 
		 * @param string $sConnectionId the ID
		 * @throws ConfigurationException if no connection with this ID exists
		 * @return PDO the connection
		 */
		public function getConnection($sConnectionId) {
			
			if (! isset($this -> arrConnections[$sConnectionId])) {
				
				$this -> initConnection($sConnectionId);
			}
			
			return $this -> arrConnections[$sConnectionId];
		}
		
		
		/**
		 * Returns the default connection.
		 * 
		 * @throws ConfigurationException when no default connection was found
		 * @return PDO the default connection
		 */
		public function getDefaultConnection() {
			
			if ($this -> sDefaultConnectionId === null) {
				
				$this -> initDefaultConnection();
			}
			
			return $this -> arrConnections[$this -> sDefaultConnectionId];
		}

		
		private function initDefaultConnection() {
			
			// iterate all known connection configs
			foreach ($this -> arrConnectionDefinitions as $objConnectionElement) {
				
				// the default connection has a default attribute set to true
				if ($objConnectionElement -> hasAttribute('pdo-db', 'default') &&
				    $objConnectionElement -> getAttribute('pdo-db', 'default') -> getValueAsBoolean()) {
					
				    // get the ID of the connection
				    $sDefaultConnectionId = $objConnectionElement -> getAttribute('pdo-db', 'id') -> getValue();
				    
				    // build PDO object for that connection
				    $this -> initConnection($sDefaultConnectionId);
				    
				    // assign as default connection (this must be done AFTER initializing!)
				    $this -> sDefaultConnectionId = $sDefaultConnectionId;
				    
				    // default connection found.
				    return;
				}
			}
			
			// if we come here, there was no default connection found.
			// convenience action: if there is only one connection, consider
			// this one connection as default connection.
			if (count($this -> arrConnectionDefinitions) == 1) {
				
				$objConnectionElement = $this -> arrConnectionDefinitions[0];
			    $sDefaultConnectionId = $objConnectionElement -> getAttribute('pdo-db', 'id') -> getValue();
			    $this -> initConnection($sDefaultConnectionId);
			    $this -> sDefaultConnectionId = $sDefaultConnectionId;
			    return;
			}
			
			// if we finally come here there really is no default connection
			// so throw an exception about that
			throw new ConfigurationException("No default connection was found.");
		}
		
		
		private function initConnection($sConnectionId) {
			
			// iterate all known connection configs
			foreach ($this -> arrConnectionDefinitions as $objConnectionElement) {
				
				// read the ID of the connection
				$sCurrentConnectionId = $objConnectionElement -> getAttribute('pdo-db', 'id') -> getValue();
				
				// initialize connection if ID matches
				if ($sCurrentConnectionId === $sConnectionId) {
					
					// TODO: support persistent connections
					$sDSN = $objConnectionElement -> getAttribute('pdo-db', 'dsn') -> getValue();
					
					$sUser = '';
					if ($objConnectionElement -> hasAttribute('pdo-db', 'user')) {
					
						$sUser = $objConnectionElement -> getAttribute('pdo-db', 'user') -> getValue();
					}
					
					$sPassword = '';
					if ($objConnectionElement -> hasAttribute('pdo-db', 'password')) {
					
						$sPassword = $objConnectionElement -> getAttribute('pdo-db', 'password') -> getValue();
					}
					
					// log some information
					Tekuna :: getLogger(__CLASS__) -> info("Initializing PDO Database Connection for DSN '$sDSN'.");
							
					// create the PDO object
					$objPdoConnection = new PDO($sDSN, $sUser, $sPassword);
					$objPdoConnection -> setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);	
					$this -> arrConnections[$sConnectionId] = $objPdoConnection;
					
					// we're done here
					return;
				}
			}
			
			// the specified connection is not known
			throw new ConfigurationException("The connection '$sConnectionId' does not exist.");
		}
	}
